package com.tang.lts.core.spi;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author Tang
 * @classname ServiceLoader
 * @description [ 模仿jdk的ServiceLoader ]
 * @date 2022/4/17 22:24
 */
public class ServiceLoader {

    private static final String LTS_DIRECTORY = "meta-inf/lts/";
    private static final String LTS_INTERNAL = "internal";
    private static final String LTS_INTERNAL_DIRECTORY = LTS_DIRECTORY + LTS_INTERNAL + "/";

    private static final ConcurrentHashMap<Class<?>, ServiceProvider> serviceMap = new ConcurrentHashMap<>();

    public static <T> T loadDefault(Class<T> clazz) {
        return load(clazz, "");
    }

    private static <T> T load(Class<T> clazz, String name) {
        ServiceProvider serviceProvider = getServiceProvider(clazz);
        ServiceDefinition serviceDefinition = serviceProvider.nameMaps.get(name);
        try {
            synchronized (serviceDefinition) {
                String clazzName = serviceDefinition.clazz;
                ClassLoader classLoader = serviceDefinition.classLoader;
                T cast = clazz.cast(Class.forName(clazzName).newInstance());
                return cast;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static ServiceProvider getServiceProvider(Class<?> tClass) {
        ServiceProvider serviceLoader = serviceMap.get(tClass);
        if (serviceLoader == null) {
            getServiceProviders(tClass);
            serviceLoader = serviceMap.get(tClass);
        }
        return serviceLoader;
    }

    public static Set<String> getServiceProviders(Class<?> tClass) {
        if (!tClass.isAnnotationPresent(SPI.class)) {
            throw new IllegalArgumentException("type(" + tClass +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        SPI spi = tClass.getAnnotation(SPI.class);
        String value = spi.dftValue();
        String key = spi.key();

        // 搜索指定文件夹下 的文件,进行封装
        Set<URLDefinition> uriDefinitions = new HashSet<>();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        uriDefinitions.addAll(collectExtensionUrls(LTS_DIRECTORY + tClass.getName(), classLoader));
        uriDefinitions.addAll(collectExtensionUrls(LTS_INTERNAL_DIRECTORY + tClass.getName(), classLoader));

        // 封装加载出来的类信息
        final ConcurrentHashMap<String, ServiceDefinition> serviceDefinitions = new ConcurrentHashMap<>();
        for (URLDefinition uriDefinition : uriDefinitions) {
            serviceDefinitions.putAll(parse(uriDefinition));
        }

        // 封装这个接口类的spi
        ServiceProvider serviceProvider = new ServiceProvider(tClass, value, serviceDefinitions);
        serviceMap.remove(tClass);
        serviceMap.put(tClass, serviceProvider);
        return serviceDefinitions.keySet();

    }

    private static HashMap<String, ServiceDefinition> parse(URLDefinition urlDefinition) {
        HashMap<String, ServiceDefinition> serviceDefinitions = new HashMap<>();
        BufferedReader bufferedReader = null;
        try {
            URL url = urlDefinition.uri.toURL();
            bufferedReader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
            while (true) {
                String line = bufferedReader.readLine();
                if (line == null) {
                    break;
                }
                int comment = line.indexOf('#');
                if (comment >= 0) {
                    line = line.substring(0, comment);
                }
                line = line.trim();
                if (line.length() == 0) {
                    continue;
                }
                int i = line.indexOf('=');
                if (i > 0) {
                    String name = line.substring(0, i).trim();
                    String clazz = line.substring(i + 1).trim();
                    // 加载到 map 中
                    serviceDefinitions.put(name, new ServiceDefinition(name, clazz, urlDefinition.classLoader));
                }
            }
        } catch (Exception exception) {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return serviceDefinitions;
    }

    /**
     * 扫描到的类信息
     */
    private static final class ServiceDefinition {

        private final String name;

        private final String clazz;

        private final ClassLoader classLoader;

        private ServiceDefinition(String name, String clazz, ClassLoader classLoader) {
            this.name = name;
            this.clazz = clazz;
            this.classLoader = classLoader;
        }

    }

    private static Set<URLDefinition> collectExtensionUrls(String resourceName, ClassLoader classLoader) {
        Enumeration<URL> configs = null;
        try {
            if (classLoader != null) {
                configs = classLoader.getResources(resourceName);
            } else {
                configs = ClassLoader.getSystemResources(resourceName);
            }
            Set<URLDefinition> urlDefinitions = new HashSet<>();
            while (configs.hasMoreElements()) {
                URL url = configs.nextElement();
                URI uri = url.toURI();
                // 找到合适的类加载器
                ClassLoader highestClassLoader = findHighestReachableClassLoader(url, classLoader, resourceName);
                urlDefinitions.add(new URLDefinition(uri, highestClassLoader));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Collections.emptySet();
    }

    private static ClassLoader findHighestReachableClassLoader(URL url, ClassLoader classLoader, String resourceName) {
        if (classLoader.getParent() == null) {
            return classLoader;
        }
        ClassLoader highestClassLoader = classLoader;

        ClassLoader current = classLoader;

        while (current.getParent() != null) {
            ClassLoader parent = current.getParent();
            try {
                Enumeration<URL> resources = parent.getResources(resourceName);
                if (resources != null) {
                    while (resources.hasMoreElements()) {
                        URL resourceURL = resources.nextElement();
                        if (url.toURI().equals(resourceURL.toURI())) {
                            highestClassLoader = parent;
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            current = parent;
        }
        return highestClassLoader;
    }

    private static final class URLDefinition {

        private final URI uri;

        private final ClassLoader classLoader;

        public URLDefinition(URI uri, ClassLoader classLoader) {
            this.uri = uri;
            this.classLoader = classLoader;
        }
    }

    private static final class ServiceProvider {
        private final Class<?> clazz;
        private final String defaultName;
        private final ConcurrentMap<String, ServiceDefinition> nameMaps;

        public ServiceProvider(Class<?> clazz, String defaultName, ConcurrentMap<String, ServiceDefinition> nameMaps) {
            this.clazz = clazz;
            this.defaultName = defaultName;
            this.nameMaps = nameMaps;
        }
    }

}